package org.yagnus.langutils;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.yagnus.exceptions.DisallowedOperationException;
import org.yagnus.interfaces.Freezable;
/**
 *
 * @author hc.busy
 *
 * This map implements the map interface by adding several methods for dealing with
 * the accesses to the map asking for a key that is not in the map.
 *
 * This implementation is not thread-safe.
 *
 */
public class BasicAccessibleMap<K, V> implements AccessibleMap<K, V>, Freezable {

    final private Map<K, V> backing;//the actual map backing this map

    //Standard map declarations
    /**
     * This is the default allocate uses HashMap as backing.
     */
    public BasicAccessibleMap() {
        backing = new HashMap<K, V>();
    }

    /**
     * uses default map backing but sets default value to the specfied value
     * @param dv
     */
    public BasicAccessibleMap(V dv) {
        backing = new HashMap<K, V>();
        defaultValue = dv;
    }

    /**
     * This is constructor requries to specify the map backing the quickie
     * and a default item.
     */
    public BasicAccessibleMap(Map<K, V> m, V dv) {
        backing = m;
        defaultValue = dv;
    }

    public final void clear() {
        backing.clear();
    }

    public final boolean containsKey(final Object key) {
        return backing.containsKey(key);
    }

    public final boolean containsValue(final Object value) {
        return backing.containsValue(value);
    }

    public final Set<Entry<K, V>> entrySet() {
        return backing.entrySet();
    }

    public final V get(final Object key) {
        return backing.get(key);
    }

    public final boolean isEmpty() {
        return backing.isEmpty();
    }

    public final Set<K> keySet() {
        return backing.keySet();
    }

    public final V put(final K key, final V value) {
        if (!frozen) {
            return backing.put(key, value);
        } else {
            throw new DisallowedOperationException();
        }
    }

    public final void putAll(final Map<? extends K, ? extends V> t) {
        if (!frozen) {
            backing.putAll(t);
        } else {
            throw new DisallowedOperationException();
        }
    }

    public final V remove(
            final Object key) {
        if (!frozen) {
            return backing.remove(key);
        } else {
            throw new DisallowedOperationException();
        }
    }

    public final int size() {
        return backing.size();
    }

    public final Collection<V> values() {
        return backing.values();
    }
    V defaultValue;

    //Quickie declarations
    /**
     * Sets a default vaule that is retrieved when map normally returns null due to missing entry
     * @param d
     */
    public final void setDefault(final V d) {
        if (frozen) {
            throw new DisallowedOperationException();
        } else {
            defaultValue = d;
        }
    }

    /**
     *
     * Get the current default value.
     * @param d
     */
    public final V getDefault() {
        return defaultValue;
    }

    /**
     * Try to get the entry mapped to key k, if not found return default value.s
     * @param k
     * @return
     */
    public final V getWithDefault(final K k) {
        V ret = backing.get(k);
        if (ret == null) {
            ret = defaultValue;
        }
        return ret;
    }

    /**
     * tries to get the key, if it is not mapped, return v instead. but do not update the map.
     * @param k The key to find
     * @param v The value to return in case key is not found.
     * @return what k was mapped to or v is k was nt found.
     */
    public final V getWithDefault(final K k, final V v) {
        V ret = backing.get(k);
        if (ret == null) {
            ret = v;
        }
        return ret;
    }

    /**
     * This method is copied from python syntax.
     * returns the value mapped to k in the map if k was mapped; otherwise map k to v, and return v.
     * @param k the key to find
     * @param v the value to map k to (and return) if k was not mapped.
     * @return the value k is mapped to at end of the
     */
    public final V getSetToDefault(final K k, final V v) {
        V ret = backing.get(k);
        if (ret == null) {
            backing.put(k, v);
            ret = v;
        }
        return ret;
    }

    /**
     * override this method to provide a way to allocate a new default item lazily.
     * @return the allocate item.
     */
    protected V allocate() {
        return null;
    }

    /**
     * Override this method to provide an allocate that allocates based on the key
     * @param k
     * @return
     */
    protected V allocate(K k){
        return allocate();
    }

    /**
     * Allocate a new one if k is not found
     *
     * @param k
     * @return
     */
    public final V getAllocate(final K k) {
        V ret = backing.get(k);
        if (ret == null) {
            if (frozen) {
                throw new DisallowedOperationException();
            }
            ret = allocate(k);
            backing.put(k, ret);
        }
        return ret;
    }
    boolean frozen = false;

    public boolean freeze() {
        frozen = true;
        return true;
    }

    public boolean unfreeze() {
        frozen = false;
        return true;
    }

    public boolean isFrozen(){
        return frozen;
    }
}
